// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/public/test/content_browser_test.h"

#include "base/command_line.h"
#include "base/location.h"
#include "base/process/launch.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/launcher/test_launcher.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_launcher.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/common/shell_switches.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

// Disabled on official builds because symbolization in sandboxes processes
// opens up security holes.
// On Android symbolization happens in one step after all the tests ran, so this
// test doesn't work there.
// TODO(mac): figure out why symbolization doesn't happen in the renderer.
// http://crbug.com/521456
// TODO(win): send PDB files for component build. http://crbug.com/521459
#if !defined(OFFICIAL_BUILD) && !defined(OS_ANDROID) && !defined(OS_MACOSX) && !(defined(COMPONENT_BUILD) && defined(OS_WIN))

IN_PROC_BROWSER_TEST_F(ContentBrowserTest, MANUAL_ShouldntRun)
{
    // Ensures that tests with MANUAL_ prefix don't run automatically.
    ASSERT_TRUE(false);
}

class CrashObserver : public RenderProcessHostObserver {
public:
    CrashObserver(const base::Closure& quit_closure)
        : quit_closure_(quit_closure)
    {
    }
    void RenderProcessExited(RenderProcessHost* host,
        base::TerminationStatus status,
        int exit_code) override
    {
        ASSERT_TRUE(status == base::TERMINATION_STATUS_PROCESS_CRASHED || status == base::TERMINATION_STATUS_ABNORMAL_TERMINATION);
        quit_closure_.Run();
    }

private:
    base::Closure quit_closure_;
};

IN_PROC_BROWSER_TEST_F(ContentBrowserTest, MANUAL_RendererCrash)
{
    scoped_refptr<MessageLoopRunner> message_loop_runner = new MessageLoopRunner;
    CrashObserver crash_observer(message_loop_runner->QuitClosure());
    shell()->web_contents()->GetRenderProcessHost()->AddObserver(&crash_observer);

    NavigateToURL(shell(), GURL("chrome:crash"));
    message_loop_runner->Run();
}

// Tests that browser tests print the callstack when a child process crashes.
IN_PROC_BROWSER_TEST_F(ContentBrowserTest, RendererCrashCallStack)
{
    base::ThreadRestrictions::ScopedAllowIO allow_io_for_temp_dir;
    base::ScopedTempDir temp_dir;
    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    base::CommandLine new_test = base::CommandLine(base::CommandLine::ForCurrentProcess()->GetProgram());
    new_test.AppendSwitchASCII(base::kGTestFilterFlag,
        "ContentBrowserTest.MANUAL_RendererCrash");
    new_test.AppendSwitch(kRunManualTestsFlag);
    new_test.AppendSwitch(kSingleProcessTestsFlag);

    // Per https://www.chromium.org/developers/testing/addresssanitizer, there are
    // ASAN bots that run without the sandbox which this test will pass for. The
    // other ones pipe the output to a symbolizer script.
    if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoSandbox)) {
        new_test.AppendSwitch(switches::kNoSandbox);
    } else {
#if defined(ADDRESS_SANITIZER)
        LOG(INFO) << "Couldn't run ContentBrowserTest.RendererCrashCallStack since "
                  << "sandbox is enabled and ASAN requires piping to an external "
                  << "script.";
        return;
#endif
    }

    std::string output;
    base::GetAppOutputAndError(new_test, &output);

    std::string crash_string = "content::RenderFrameImpl::PrepareRenderViewForNavigation";

    if (output.find(crash_string) == std::string::npos) {
        GTEST_FAIL() << "Couldn't find\n"
                     << crash_string << "\n in output\n "
                     << output;
    }
}

IN_PROC_BROWSER_TEST_F(ContentBrowserTest, MANUAL_BrowserCrash)
{
    CHECK(false);
}

// Tests that browser tests print the callstack on asserts.
IN_PROC_BROWSER_TEST_F(ContentBrowserTest, BrowserCrashCallStack)
{
    base::ThreadRestrictions::ScopedAllowIO allow_io_for_temp_dir;
    base::ScopedTempDir temp_dir;
    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    base::CommandLine new_test = base::CommandLine(base::CommandLine::ForCurrentProcess()->GetProgram());
    new_test.AppendSwitchASCII(base::kGTestFilterFlag,
        "ContentBrowserTest.MANUAL_BrowserCrash");
    new_test.AppendSwitch(kRunManualTestsFlag);
    new_test.AppendSwitch(kSingleProcessTestsFlag);
    std::string output;
    base::GetAppOutputAndError(new_test, &output);

    std::string crash_string = "content::ContentBrowserTest_MANUAL_BrowserCrash_Test::RunTestOnMainThread";

    if (output.find(crash_string) == std::string::npos) {
        GTEST_FAIL() << "Couldn't find\n"
                     << crash_string << "\n in output\n "
                     << output;
    }
}

#endif

class ContentBrowserTestSanityTest : public ContentBrowserTest {
public:
    void SetUpCommandLine(base::CommandLine* command_line) override
    {
        const testing::TestInfo* const test_info = testing::UnitTest::GetInstance()->current_test_info();
        if (std::string(test_info->name()) == "SingleProcess")
            command_line->AppendSwitch(switches::kSingleProcess);
    }

    void Test()
    {
        GURL url = GetTestUrl(".", "simple_page.html");

        base::string16 expected_title(base::ASCIIToUTF16("OK"));
        TitleWatcher title_watcher(shell()->web_contents(), expected_title);
        NavigateToURL(shell(), url);
        base::string16 title = title_watcher.WaitAndGetTitle();
        EXPECT_EQ(expected_title, title);
    }
};

IN_PROC_BROWSER_TEST_F(ContentBrowserTestSanityTest, Basic)
{
    Test();
}

IN_PROC_BROWSER_TEST_F(ContentBrowserTestSanityTest, SingleProcess)
{
    Test();
}

namespace {

    void CallbackChecker(bool* non_nested_task_ran)
    {
        *non_nested_task_ran = true;
    }

} // namespace

IN_PROC_BROWSER_TEST_F(ContentBrowserTest, NonNestableTask)
{
    bool non_nested_task_ran = false;
    base::ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
        FROM_HERE, base::Bind(&CallbackChecker, &non_nested_task_ran));
    content::RunAllPendingInMessageLoop();
    ASSERT_TRUE(non_nested_task_ran);
}

} // namespace content
